<template>
    <div class="chatting-widgets">
        <div class="headers-wrapper">
            <div class="flex flex-1 items-center">
                <photos width="26px" height="26px" radius="6px" :src="robotDetail.logo"/>
                <h4 class="mx-2 text-lg">{{ sessionName }}</h4>
                <div class="tag">
                    <icon name="el-icon-Clock" :size="13" />
                    <span class="ml-1">{{ historyList.length }}条记录</span>
                </div>
                <div class="tag !bg-lighter cursor-pointer" @click="handleCollect">
                    <icon
                        v-if="robotDetail.is_collect"
                        name="el-icon-StarFilled"
                        :size="15"
                    />
                    <icon
                        v-else
                        name="el-icon-Star"
                        :size="15"
                    />
                    <span class="ml-1">{{ robotDetail.collect_num }}</span>
                </div>
            </div>
            <div class="flex items-center">
                <el-tooltip placement="top" content="会话窗">
                    <div class="tool-item" @click="sessionShow = true">
                        <icon name="el-icon-Clock" size="16" />
                    </div>
                </el-tooltip>
                <el-tooltip placement="top" content="重命名">
                    <div class="tool-item" @click="handleSessionRename">
                        <icon name="el-icon-EditPen" size="16" />
                    </div>
                </el-tooltip>
                <el-tooltip placement="top" content="新对话">
                    <div class="tool-item" @click="handleSessionNews">
                        <icon name="el-icon-ChatDotSquare" size="16" />
                    </div>
                </el-tooltip>
            </div>
        </div>
        <div class="history-wrapper" v-loading="pageLoading" element-loading-text="加载中...">
            <el-watermark
                class="flex h-full"
                :content="winWatermark"
                :font="{
                    color: isDark
                        ? 'rgba(256,256,256,0.08)'
                        : 'rgba(0,0,0,0.06)',
                    fontSize: 12
                }"
            >
                <el-scrollbar ref="scrollbarRef" height="100%" class="flex-1" @scroll="handleScroll">
                    <div ref="innerRef" class="px-7">
                        <chatting-message
                            v-if="robotDetail.welcome_text"
                            type="answer"
                            :recordId="0"
                            :avatar="robotDetail.logo"
                            :content="robotDetail.welcome_text"
                            :createTime="currentTime"
                        />
                        <chatting-message
                            v-for="(item, index) in historyList"
                            :key="index"
                            :loading="item.loading"
                            :recordId="item.id"
                            :type="item.type"
                            :avatar="item.avatar"
                            :thinks="item.thinks"
                            :content="item.content"
                            :context="item.context"
                            :quotes="item.quotes"
                            :issues="item.issues"
                            :attach="item.attach"
                            :annex="item.annex"
                            :webSearch="item.web_search"
                            :toolsList="item.tools_list"
                            :taskTime="item.task_time"
                            :createTime="item.create_time"
                            :chatModel="item.chat_model"
                            :isShowCopy="true"
                            :isShowVoice="true"
                            :isShowShare="true"
                            :isShowTimed="robotDetail.is_show_timed"
                            :isShowQuote="robotDetail.is_show_quote"
                            :isShowContext="robotDetail.is_show_context"
                            :isContextClear="!!item.end_clear"
                            :isShowRewrite="index === historyList.length - 1"
                            :isShowIssues="index === historyList.length - 1"
                            :isShowChatModel="true"
                            @rewrite="rewriteChat"
                            @search="handleSearch"
                            @stop="stopStream"
                            @ask="sendAsk"
                        />
                    </div>
                </el-scrollbar>

                <div
                    v-if="webSearchShow"
                    class="pt-6 pr-6 pl-2 w-[470px]"
                >
                    <chatting-web-search
                        :lists="webSearchList"
                        @close="webSearchShow = false"
                    />
                </div>
            </el-watermark>
        </div>

        <div class="my-[15px] mx-[30px]">
            <chatting-input-box
                ref="chatActionRef"
                :loading="isReceiving"
                :instruct="robotDetail.instruct"
                :maxUploadNum="robotDetail.max_upload_num"
                :isUploadFiles="robotDetail.is_upload_files"
                :isUploadImage="robotDetail.is_upload_image"
                :isWebSearch="robotDetail.is_web_search"
                :isShowClear="true"
                @search="(e: boolean) => webSearchOpen = e"
                @clear="handleClear"
                @sending="sendChats"
            />
        </div>

        <the-session
            :show="sessionShow"
            :bot="robotDetail.code"
            :sid="sessionId"
            :lists="sessionList"
            @close="sessionShow = false"
            @change="queryHistoryList"
            @update="querySessionList"
        />
    </div>
</template>

<script setup lang="ts">
import { ElScrollbar } from 'element-plus'
import { useElementSize } from '@vueuse/core'
import { debounce } from 'lodash-es'
import useAppStore from '~/stores/app'
import useUserStore from '~/stores/user'
import useConfStore from '~/stores/conf'
import datetimeUtil from '~/utils/datetime'
import chatDialogApi from '~/api/agent/chat/dialog'
import chatRoomingApi from '~/api/agent/chat/rooming'
import chatSessionApi from '~/api/agent/chat/session'
import robotSmarterApi from '~/api/agent/robot/smarter'
import TheSession from './TheSession.vue'

const props = defineProps({
    // 智能体编号
    bot: {
        type: String,
        default: ''
    },
    // 初始的问题
    ask: {
        type: String,
        default: ''
    },
    // 初始的消息
    msg: {
        type: Array,
        default: () => []
    },
    // 初始的附件
    attach: {
        type: Array,
        default: () => []
    },
    // 初始的搜索
    search: {
        type: Boolean,
        default: () => false
    }
})

const appStore = useAppStore()
const userStore = useUserStore()
const confStore = useConfStore()

// 暗黑模式
const isDark = computed(() => confStore.isDarkColor)

// 窗口水印
const winWatermark = computed(() => appStore.getDialogConfig?.win_watermark)

// 节点Ref
const chatActionRef = shallowRef()
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const innerRef = ref<HTMLDivElement>()

// 会话信息
const sessionId = ref<number>(0)
const sessionName = ref<string>('')
const sessionShow = ref<boolean>(false)
const sessionList = ref<ChatSessionListResponse[]>()
const historyList = ref<ChatRecordsListResponse[]>([])
const robotDetail = ref({} as ChatRoomingInitResponse)
const currentTime = datetimeUtil.datetime()

// 记录分页
const pageNo = ref<number>(1)
const pageMore = ref<boolean>(true)
const pageLoading = ref<boolean>(false)

// 联网搜索
const webSearchOpen = ref<boolean>(false)
const webSearchShow = ref<boolean>(false)
const webSearchList = ref<ChatRecordsWebSearch[]>([])

// 正在接收
const isReceiving = ref<boolean>(false)
// 流式对象
let streamReader: ReadableStreamDefaultReader<Uint8Array> | null = null

/**
 * 触顶加载更多
 */
const handleScroll = debounce(async (event: any) => {
    const { scrollTop } = event
    if (scrollTop <= 50 && !pageLoading.value && pageMore.value) {
        pageLoading.value = true
        try {
            const newData = await chatDialogApi.history({
                page_no: pageNo.value + 1,
                bot: robotDetail.value.code,
                sid: sessionId.value,
                scene: 'normal'
            })
            if (newData.length === 0) {
                pageMore.value = false
            } else {
                pageNo.value += 1
                historyList.value = [...newData, ...historyList.value]
            }
        } finally {
            pageLoading.value = false
        }
    }
}, 100)

/**
 * 获取对话记录
 */
const queryHistoryList = async (sid?: number, name?: string) => {
    if (sid !== undefined) {
        sessionId.value = sid
    }

    if (name !== undefined) {
        sessionName.value = name
    }

    pageLoading.value = true
    try {
        historyList.value = await chatDialogApi.history({
            page_no: pageNo.value,
            bot: robotDetail.value.code,
            sid: sessionId.value,
            scene: 'normal'
        })

        await nextTick(() => {
            scrollToBottom()
        })
    } finally {
        pageLoading.value = false
    }
}

/**
 * 获取会话窗口
 */
const querySessionList = async (keyword?: string) => {
    sessionList.value = await chatSessionApi.lists(props.bot, keyword)
    if (sessionList.value.length > 0) {
        sessionId.value = sessionList.value[0]?.id || 0
        sessionName.value = sessionList.value[0]?.name || ''
        await queryHistoryList()
    } else {
        sessionId.value = 0
        sessionName.value = '新的会话'
        historyList.value = []
    }
}

/**
 * 会话窗重命名
 */
const handleSessionRename = async () => {
    await feedback.prompt('', '重命名', {inputValue: sessionName.value })
        .then(async ({ value }) => {
            if (value !== sessionName.value) {
                await chatSessionApi.edit(sessionId.value, value)
                sessionName.value = value
                sessionList.value = await chatSessionApi.lists(props.bot)
            }
            feedback.msgSuccess('重命名成功')
        }).catch(() => {})
}

/**
 * 新建会话窗口
 */
const handleSessionNews = async () => {
    if (historyList.value.length <= 0) {
        return feedback.msgWarning('当前已是新对话')
    }
    await chatSessionApi.add(props.bot, '新的会话')
    await querySessionList()
}

/**
 * 收藏/取消收藏
 */
const handleCollect = async () => {
    await robotSmarterApi.collect(props.bot)
    if (robotDetail.value.is_collect) {
        feedback.msgSuccess('取消成功')
    } else {
        feedback.msgSuccess('收藏成功')
    }
    robotDetail.value = await chatRoomingApi.init(props.bot)
}

/**
 * 清空对话上下文
 */
const handleClear = async () => {
    if (isReceiving.value) {
        feedback.msgError('请耐心等待回复完毕再操作!')
    } else {
        feedback.confirm('确定要清空当前对话记录吗?')
            .then(async () => {
                await chatDialogApi.clear({
                    scene: 'normal',
                    bot: props.bot,
                    sid: sessionId.value
                })
                const chatNum = historyList.value.length
                if (chatNum > 0 && historyList.value.length > 0) {
                    historyList.value[chatNum - 1]!.end_clear = 1
                }
                feedback.msgSuccess('历史对话已清空!')
                await nextTick(() => {
                    scrollToBottom()
                })
            }).catch(() => {})
    }
}

/**
 * 重新回答问题
 */
const { lockFn: rewriteChat } = useLockFn(async () => {
    const last = historyList.value[historyList.value.length - 1]
    if (last) {
        const userInput = historyList.value.find(({id}) => id === last.id)
        if (userInput) {
            historyList.value.splice(historyList.value.length - 2, 2)
            await sendChats(
                userInput.content,
                [{'role': 'user', 'content': userInput.content}],
                userInput.attach || []
            )
        }
    }
})

/**
 * 停止流式对话
 */
const stopStream = () => {
    if (streamReader) {
        streamReader.cancel()
    }
}

/**
 * 发起建议提问
 */
const sendAsk = async (question: string) => {
    await sendChats(
        question,
        [{'role': 'user', 'content': question}],
        []
    )
}

/**
 * 发送流式对话
 */
const sendChats = async (question: string, messages: any[], files: any[]) => {
    if (isReceiving.value) {
        feedback.msgWarning('请等回复后再发送哦~')
        return
    }

    if (!sessionId.value) {
        await chatSessionApi.add(props.bot, question)
        await querySessionList()
    }

    const datetime = datetimeUtil.datetime()
    historyList.value.push({
        id: 0,
        type: 'question',
        avatar: userStore.users.avatar,
        content: question,
        create_time: datetime,
        attach: files
    })

    historyList.value.push({
        id: 0,
        type: 'answer',
        avatar: robotDetail.value.logo,
        thinks: '',
        content: '',
        attach: [],
        web_search: [],
        tools_list: [],
        create_time: datetime,
        loading: true
    })

    isReceiving.value = true
    try {
        await chatDialogApi.sendChat({
            sid: sessionId.value,
            bot: props.bot,
            scene: 'normal',
            stream: true,
            search: webSearchOpen.value,
            messages: messages
        }, {
            onStart(reader) {
                chatActionRef.value?.setInputValue()
                streamReader = reader
            },
            onMessage(value) {
                value.trim().split('data:').forEach(async (text) => {
                    if (text !== '' && text !== '[DONE]') {
                        try {
                            const dataJson = JSON.parse(text)
                            if (dataJson.error) {
                                feedback.msgError(dataJson.error.message)
                            }

                            const { id, event, choices } = dataJson
                            const finish = choices[0]?.finish_reason
                            const answer = choices[0]?.delta.content
                            const thinks = choices[0]?.delta.reasoning_content
                            const toolCalls = choices[0]?.delta.tool_calls

                            let chatIndex = historyList.value.findIndex((item: any) => item.id === id)

                            if (chatIndex === -1) {
                                chatIndex = historyList.value.length - 1
                                if (id) {
                                    historyList.value[chatIndex]!.id = id
                                }
                            }

                            if (thinks) {
                                historyList.value[chatIndex]!.thinks += thinks
                            }

                            switch (event) {
                                case 'chat':
                                    if (answer) {
                                        historyList.value[chatIndex]!.content += answer
                                    }
                                    break
                                case 'tools':
                                    toolCalls.forEach((t: ChatRecordsToolsList) => {
                                        historyList.value[chatIndex]!.tools_list?.push(t)
                                    })
                                    break
                                case 'search':
                                    const search = answer ? JSON.parse(answer) : null
                                    historyList.value[chatIndex]!.web_search = search
                                    break
                                case 'image':
                                case 'video':
                                case 'audio':
                                case 'file':
                                    const results = JSON.parse(answer)
                                    if (Array.isArray(results)) {
                                        results.forEach((item) => {
                                            historyList.value[chatIndex]!.attach?.push(item)
                                        })
                                    } else {
                                        historyList.value[chatIndex]!.attach?.push(results)
                                    }
                                    break
                            }

                            if (finish === 'normal') {
                                historyList.value[chatIndex]!.loading = false
                            }
                        } catch {}
                    }
                })
            },
            onClose() {
                scrollToBottom()
                setTimeout(queryHistoryList, 1200)
                isReceiving.value = false
            }
        })
    } catch {
        isReceiving.value = false
        streamReader && await streamReader.cancel()
    }

    await nextTick(() => {
        scrollToBottom()
    })
}

/**
 * 联网搜索显示
 */
const handleSearch = (data: any[]) => {
    webSearchShow.value = !webSearchShow.value
    webSearchList.value = data
}

/**
 * 滚动条到底部
 */
const scrollToBottom = () => {
    const scrollHeight: number | any = scrollbarRef.value?.wrapRef?.scrollHeight
    const clientHeight: number | any = scrollbarRef.value?.wrapRef?.clientHeight
    const scrollH = scrollHeight - clientHeight
    scrollbarRef.value?.setScrollTop(scrollH)
}

/**
 * 监听高度变化
 */
const { height } = useElementSize(innerRef)
watch(height, () => {
    if (isReceiving.value) {
        scrollToBottom()
    }
})

/**
 * 监听应用变化
 */
watch(
    () => props.bot,
    async (val) => {
        if (val) {
            robotDetail.value = await chatRoomingApi.init(props.bot)
            await querySessionList()
        }
    },
    {
        immediate: true
    }
)

/**
 * 初始化提问
 */
watch(
    () => props.ask,
    (val) => {
        if (val && props.bot) {
            webSearchOpen.value = props.search
            sendChats(
                val,
                props.msg,
                props.attach
            )
        }
    }, { immediate: true }
)
</script>

<style scoped lang="scss">
.chatting-widgets {
    display: flex;
    flex: 1;
    flex-direction: column;
    overflow: hidden;
    .headers-wrapper {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 10px 20px;
        border-bottom: 1px solid var(--el-border-color-lighter);
        .tool-item {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 30px;
            height: 30px;
            margin-left: 15px;
            color: var(--el-text-color-placeholder);
            border: 1px solid var(--el-border-color-lighter);
            border-radius: 10px;
            &:hover {
                cursor: pointer;
                background-color: var(--el-color-hover);
                border-color: var(--el-color-primary);
            }
        }
    }

    .history-wrapper {
        flex: 1;
        overflow: hidden;
    }

    .tag {
        display: inline-flex;
        align-items: center;
        padding: 4px 10px;
        margin: 0 5px;
        font-size: 12px;
        color: var(--el-color-primary);
        background-color: var(--el-color-primary-light-9);
        border-radius: 6px;
    }
}
</style>
